Previous slide Next slide Toggle fullscreen Open presenter view
Программирование сетевых приложений
Типы исключительных ситуаций и процесс их обработки в C++ и Qt
Программирование сетевых приложений
Содержание лекции
Введение в исключительные ситуации
Необходимость обработки исключений
Основные принципы обработки исключений
Типы исключений
Обработка исключительных ситуаций
Объекты исключительных ситуаций
Стандартные исключительные ситуации
Пользовательские исключения
Особенности обработки исключений в Qt
Лучшие практики
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Введение в исключительные ситуации
Исключительные ситуации (exceptions) являются критически важным механизмом обработки ошибок в современном программировании. Они позволяют разработчикам создавать надежные и устойчивые приложения, способные корректно реагировать на непредвиденные ситуации во время выполнения программы.
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Необходимость обработки исключительных ситуаций
Проблемы традиционной обработки ошибок
До появления исключений обработка ошибок в C++ основывалась на следующих подходах:
Коды возврата функций int result = someFunction ();
if (result != 0 ) {
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Глобальные переменные состояния
errno = 0 ;
FILE* file = fopen ("data.txt" , "r" );
if (errno != 0 ) {
}
Флаги ошибок
class MyClass {
private :
bool errorFlag;
std::string errorMessage;
public :
bool hasError () const { return errorFlag; }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Преимущества исключений
Исключения решают многие проблемы традиционных подходов:
Разделение кода основной логики и обработки ошибок
Принудительная обработка критических ошибок
Возможность передачи ошибок через несколько уровней стека вызовов
Гарантированная очистка ресурсов (RAII)
Типобезопасность при обработке различных типов ошибок
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Основные принципы обработки исключений
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
1. Принцип RAII (Resource Acquisition Is Initialization)
class FileHandler {
private :
FILE* file;
public :
FileHandler (const std::string& filename) {
file = fopen (filename.c_str (), "r" );
if (!file) {
throw std::runtime_error ("Не удалось открыть файл" );
}
}
~FileHandler () {
if (file) {
fclose (file);
}
}
FileHandler (const FileHandler&) = delete ;
FileHandler& operator =(const FileHandler&) = delete ;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
2. Принцип безопасности исключений
Базовая гарантия (Basic Guarantee): Программа остается в согласованном состоянии, не происходит утечек ресурсов.
Сильная гарантия (Strong Guarantee): Операция либо завершается успешно, либо оставляет программу в исходном состоянии.
Гарантия отсутствия исключений (No-throw Guarantee): Операция гарантированно не выбрасывает исключений.
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
3. Принцип специфичности
Обрабатывайте конкретные типы исключений, а не общие:
try {
} catch (...) {
}
try {
} catch (const std::invalid_argument& e) {
} catch (const std::out_of_range& e) {
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Типы исключений
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
1. Стандартные исключения C++
Стандартная библиотека C++ предоставляет иерархию исключений:
#include <stdexcept>
#include <iostream>
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ └── std::out_of_range
├── std::runtime_error
│ ├── std::range_error
│ ├── std::overflow_error
│ └── std::underflow_error
└── std::bad_alloc
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
2. Примеры стандартных исключений
#include <stdexcept>
#include <vector>
#include <memory>
void demonstrateStandardExceptions () {
try {
int parseInt (const std::string& str) {
if (str.empty ()) {
throw std::invalid_argument ("Пустая строка" );
}
return std::stoi (str);
}
std::vector<int > vec = {1 , 2 , 3 };
int value = vec.at (10 );
try {
int * hugeArray = new int [1000000000000 ];
} catch (const std::bad_alloc& e) {
std::cerr << "Ошибка выделения памяти: " << e.what () << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Стандартное исключение: " << e.what () << std::endl;
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Обработка исключительных ситуаций
Базовый синтаксис
try {
} catch (const ExceptionType& e) {
} catch (...) {
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Продвинутые техники обработки
1. Повторное выбрасывание исключений
void processData () {
try {
riskyOperation ();
} catch (const std::exception& e) {
logError (e.what ());
throw ;
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
2. Спецификация исключений (устарело в C++11)
void oldStyleFunction () throw (std::runtime_error) {
}
void modernFunction () noexcept {
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
3. Группировка исключений
template <typename T>
class Container {
public :
void insert (const T& value) {
try {
if (value < 0 ) {
throw std::invalid_argument ("Отрицательное значение" );
}
if (data_.size () >= max_size_) {
throw std::length_error ("Превышен максимальный размер" );
}
data_.push_back (value);
} catch (const std::logic_error& e) {
std::cerr << "Логическая ошибка: " << e.what () << std::endl;
throw ;
}
}
private :
std::vector<T> data_;
size_t max_size_ = 1000 ;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Объекты исключительных ситуаций
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Создание собственных классов исключений
#include <string>
#include <chrono>
class NetworkException : public std::runtime_error {
private :
std::string host_;
int port_;
std::chrono::system_clock::time_point timestamp_;
public :
NetworkException (const std::string& message,
const std::string& host,
int port)
: std::runtime_error (message),
host_ (host),
port_ (port),
timestamp_ (std::chrono::system_clock::now ()) {}
const std::string& getHost () const { return host_; }
int getPort () const { return port_; }
std::chrono::system_clock::time_point getTimestamp () const {
return timestamp_;
}
std::string getDetailedMessage () const {
return what () + std::string (" (" ) + host_ + ":" +
std::to_string (port_) + ")" ;
}
};
void connectToServer (const std::string& host, int port) {
bool connectionFailed = true ;
if (connectionFailed) {
throw NetworkException ("Не удалось подключиться к серверу" ,
host, port);
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Иерархия пользовательских исключений
class ApplicationException : public std::exception {
protected :
std::string message_;
int errorCode_;
public :
ApplicationException (const std::string& message, int errorCode = 0 )
: message_ (message), errorCode_ (errorCode) {}
const char * what () const noexcept override {
return message_.c_str ();
}
int getErrorCode () const { return errorCode_; }
};
class DatabaseException : public ApplicationException {
private :
std::string query_;
public :
DatabaseException (const std::string& message,
const std::string& query,
int errorCode = 0 )
: ApplicationException (message, errorCode), query_ (query) {}
const std::string& getQuery () const { return query_; }
};
class SecurityException : public ApplicationException {
private :
std::string userId_;
std::string operation_;
public :
SecurityException (const std::string& message,
const std::string& userId,
const std::string& operation)
: ApplicationException (message), userId_ (userId), operation_ (operation) {}
const std::string& getUserId () const { return userId_; }
const std::string& getOperation () const { return operation_; }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Стандартные исключительные ситуации
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
void demonstrateCommonExceptions () {
try {
try {
size_t hugeSize = std::numeric_limits<size_t >::max ();
int * array = new int [hugeSize];
} catch (const std::bad_alloc& e) {
std::cout << "bad_alloc: " << e.what () << std::endl;
}
try {
std::vector<int > vec = {1 , 2 , 3 };
int value = vec.at (100 );
} catch (const std::out_of_range& e) {
std::cout << "out_of_range: " << e.what () << std::endl;
}
try {
std::stoi ("не число" );
} catch (const std::invalid_argument& e) {
std::cout << "invalid_argument: " << e.what () << std::endl;
}
try {
int result = std::accumulate (std::vector <int >(1000000 , 1000000 ).begin (),
std::vector <int >(1000000 , 1000000 ).end (), 0 );
} catch (const std::overflow_error& e) {
std::cout << "overflow_error: " << e.what () << std::endl;
}
} catch (const std::exception& e) {
std::cout << "Общее исключение: " << e.what () << std::endl;
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Определение и порождение собственных исключительных ситуаций
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Создание иерархии исключений для сетевого приложения
#include <exception>
#include <string>
#include <system_error>
class NetworkException : public std::runtime_error {
protected :
int errorCode_;
std::string details_;
public :
NetworkException (const std::string& message, int errorCode = 0 )
: std::runtime_error (message), errorCode_ (errorCode) {}
int getErrorCode () const { return errorCode_; }
virtual std::string getDetails () const { return what (); }
};
class ConnectionException : public NetworkException {
private :
std::string host_;
int port_;
public :
ConnectionException (const std::string& host, int port, int errorCode = 0 )
: NetworkException ("Ошибка подключения к " + host + ":" +
std::to_string (port), errorCode),
host_ (host), port_ (port) {}
std::string getDetails () const override {
return "Не удалось подключиться к " + host_ + ":" +
std::to_string (port) + " (код ошибки: " +
std::to_string (errorCode_) + ")" ;
}
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
class DataTransmissionException : public NetworkException {
private :
size_t bytesSent_;
size_t bytesTotal_;
public :
DataTransmissionException (size_t sent, size_t total, int errorCode = 0 )
: NetworkException ("Ошибка передачи данных" , errorCode),
bytesSent_ (sent), bytesTotal_ (total) {}
std::string getDetails () const override {
return "Передано " + std::to_string (bytesSent_) +
" из " + std::to_string (bytesTotal_) +
" байт (код ошибки: " + std::to_string (errorCode_) + ")" ;
}
};
class ProtocolException : public NetworkException {
private :
std::string protocol_;
std::string violation_;
public :
ProtocolException (const std::string& protocol,
const std::string& violation,
int errorCode = 0 )
: NetworkException ("Нарушение протокола " + protocol, errorCode),
protocol_ (protocol), violation_ (violation) {}
std::string getDetails () const override {
return "Протокол " + protocol_ + ": " + violation_;
}
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Использование пользовательских исключений
class NetworkClient {
private :
std::string host_;
int port_;
bool connected_;
public :
NetworkClient (const std::string& host, int port)
: host_ (host), port_ (port), connected_ (false ) {}
void connect () {
try {
bool connectionSuccessful = false ;
if (!connectionSuccessful) {
throw ConnectionException (host_, port_, 1001 );
}
connected_ = true ;
} catch (const ConnectionException& e) {
std::cerr << "Ошибка подключения: " << e.getDetails () << std::endl;
throw ;
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
void sendData (const std::vector<uint8_t >& data) {
if (!connected_) {
throw std::logic_error ("Клиент не подключен" );
}
try {
size_t sent = 0 ;
size_t total = data.size ();
bool transmissionFailed = true ;
if (transmissionFailed) {
throw DataTransmissionException (sent, total, 2001 );
}
} catch (const DataTransmissionException& e) {
std::cerr << "Ошибка передачи: " << e.getDetails () << std::endl;
try {
reconnect ();
} catch (const ConnectionException& reconnectError) {
std::cerr << "Не удалось восстановить соединение: "
<< reconnectError.what () << std::endl;
throw ;
}
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
private :
void reconnect () {
connected_ = false ;
connect ();
}
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Особенности обработки исключений в Qt
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Использование исключений в Qt-приложениях
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <QException>
#include <QDebug>
class QtNetworkException : public QException {
private :
QString message_;
int errorCode_;
public :
QtNetworkException (const QString& message, int errorCode = 0 )
: message_ (message), errorCode_ (errorCode) {}
void raise () const override { throw *this ; }
QtNetworkException* clone () const override {
return new QtNetworkException (*this );
}
const char * what () const noexcept override {
return message_.toLocal8Bit ().constData ();
}
QString message () const { return message_; }
int errorCode () const { return errorCode_; }
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
class SafeNetworkManager : public QObject {
Q_OBJECT
private :
QNetworkAccessManager* manager_;
public :
explicit SafeNetworkManager (QObject* parent = nullptr )
: QObject(parent), manager_(new QNetworkAccessManager(this)) { }
void makeSafeRequest (const QUrl& url) {
try {
QNetworkRequest request (url) ;
QNetworkReply* reply = manager_->get (request);
connect (reply, &QNetworkReply::finished, [reply, this ]() {
try {
handleReply (reply);
} catch (const QtNetworkException& e) {
qWarning () << "Ошибка сети:" << e.message ();
emit errorOccurred (e.message (), e.errorCode ());
} catch (const std::exception& e) {
qWarning () << "Стандартное исключение:" << e.what ();
emit errorOccurred (QString::fromLocal8Bit (e.what ()), -1 );
}
reply->deleteLater ();
});
} catch (const QtNetworkException& e) {
qWarning () << "Исключение при создании запроса:" << e.message ();
emit errorOccurred (e.message(), e.errorCode()) ;
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
signals:
void errorOccurred (const QString& message, int errorCode) ;
void dataReceived (const QByteArray& data) ;
private :
void handleReply (QNetworkReply* reply) {
if (reply->error () != QNetworkReply::NoError) {
throw QtNetworkException (
reply->errorString (),
static_cast <int >(reply->error ())
);
}
QByteArray data = reply->readAll ();
emit dataReceived (data) ;
}
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Интеграция исключений с Qt сигналами и слотами
#include <QObject>
#include <QTimer>
#include <QException>
class ExceptionSafeWorker : public QObject {
Q_OBJECT
private :
QTimer* workTimer_;
int operationCount_;
public :
explicit ExceptionSafeWorker (QObject* parent = nullptr )
: QObject(parent), operationCount_(0 ) {
workTimer_ = new QTimer (this );
connect (workTimer_, &QTimer::timeout, this , &ExceptionSafeWorker::performWork);
}
void startWork () {
workTimer_->start (1000 );
}
void stopWork () {
workTimer_->stop ();
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
private slots:
void performWork () {
try {
operationCount_++;
if (operationCount_ % 3 == 0 ) {
throw std::runtime_error ("Симулированная ошибка в работе" );
}
if (operationCount_ % 5 == 0 ) {
throw QtNetworkException ("Сетевая ошибка в работе" , 500 );
}
qDebug () << "Операция" << operationCount_ << "выполнена успешно" ;
emit workCompleted (operationCount_) ;
} catch (const QtNetworkException& e) {
qWarning () << "Сетевая ошибка в операции" << operationCount_
<< ":" << e.message ();
emit workFailed (operationCount_, e.message()) ;
} catch (const std::exception& e) {
qWarning () << "Ошибка в операции" << operationCount_
<< ":" << e.what ();
emit workFailed (operationCount_, QString::fromLocal8Bit(e.what())) ;
} catch (...) {
qCritical () << "Неизвестная ошибка в операции" << operationCount_;
emit workFailed (operationCount_, "Неизвестная ошибка" ) ;
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
signals:
void workCompleted (int operationId) ;
void workFailed (int operationId, const QString& error) ;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Лучшие практики обработки исключений
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
1. Использование RAII для управления ресурсами
class DatabaseConnection {
private :
std::unique_ptr<sqlite3, decltype (&sqlite3_close)> db_;
public :
DatabaseConnection (const std::string& dbPath)
: db_ (nullptr , sqlite3_close) {
sqlite3* rawDb = nullptr ;
int result = sqlite3_open (dbPath.c_str (), &rawDb);
if (result != SQLITE_OK) {
std::string error = sqlite3_errmsg (rawDb);
sqlite3_close (rawDb);
throw std::runtime_error ("Не удалось открыть БД: " + error);
}
db_.reset (rawDb);
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
void executeQuery (const std::string& query) {
char * errorMsg = nullptr ;
int result = sqlite3_exec (db_.get (), query.c_str (),
nullptr , nullptr , &errorMsg);
if (result != SQLITE_OK) {
std::string error (errorMsg) ;
sqlite3_free (errorMsg);
throw std::runtime_error ("Ошибка SQL: " + error);
}
}
~DatabaseConnection () = default ;
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
2. Создание безопасных интерфейсов
class SafeFile {
private :
std::FILE* file_;
std::string filename_;
public :
SafeFile (const std::string& filename, const std::string& mode)
: filename_ (filename) {
file_ = std::fopen (filename.c_str (), mode.c_str ());
if (!file_) {
throw std::runtime_error ("Не удалось открыть файл: " + filename);
}
}
SafeFile (const SafeFile&) = delete ;
SafeFile& operator =(const SafeFile&) = delete ;
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
SafeFile (SafeFile&& other) noexcept
: file_ (other.file_), filename_ (std::move (other.filename_)) {
other.file_ = nullptr ;
}
SafeFile& operator =(SafeFile&& other) noexcept {
if (this != &other) {
close ();
file_ = other.file_;
filename_ = std::move (other.filename_);
other.file_ = nullptr ;
}
return *this ;
}
void write (const std::string& data) {
if (!file_) {
throw std::logic_error ("Файл не открыт" );
}
size_t written = std::fwrite (data.c_str (), 1 , data.size (), file_);
if (written != data.size ()) {
throw std::runtime_error ("Ошибка записи в файл" );
}
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
std::string read (size_t maxSize = 1024 ) {
if (!file_) {
throw std::logic_error ("Файл не открыт" );
}
std::vector<char > buffer (maxSize) ;
size_t read = std::fread (buffer.data (), 1 , maxSize, file_);
if (std::ferror (file_)) {
throw std::runtime_error ("Ошибка чтения файла" );
}
return std::string (buffer.data (), read);
}
~SafeFile () {
close ();
}
private :
void close () noexcept {
if (file_) {
std::fclose (file_);
file_ = nullptr ;
}
}
};
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
3. Использование современных возможностей C++
#include <optional>
#include <variant>
#include <expected>
std::optional<int > safeDivide (int a, int b) {
if (b == 0 ) {
return std::nullopt ;
}
return a / b;
}
using Result = std::variant<int , std::string>;
Result calculate (int a, int b) {
if (b == 0 ) {
return std::string ("Деление на ноль" );
}
return a / b;
}
std::expected<int , std::string> safeCalculation (int a, int b) {
if (b == 0 ) {
return std::unexpected ("Деление на ноль" );
}
return a / b;
}
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Заключение
Правильная обработка исключений является основой создания надежных и устойчивых приложений на C++. Использование современных подходов, таких как RAII, создание собственных иерархий исключений и интеграция с фреймворками вроде Qt, позволяет создавать качественный код, способный корректно реагировать на непредвиденные ситуации.
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Ключевые принципы:
Используйте исключения для исключительных ситуаций, а не для обычного потока управления
Создавайте специфичные типы исключений для различных типов ошибок
Обеспечивайте безопасность исключений через RAII
Логируйте исключения для последующего анализа
Тестируйте обработку исключений в вашем коде
Типы исключительных ситуаций и процесс их обработки
Программирование сетевых приложений
Вопросы для самопроверки
Какие три уровня гарантии безопасности исключений существуют?
Чем отличается std::logic_error от std::runtime_error?
Как правильно создавать собственные иерархии исключений?
Какие преимущества дает использование RAII в обработке исключений?
Как интегрировать исключения с Qt сигналами и слотами?
Что такое noexcept и когда его следует использовать?
Какие современные альтернативы исключениям доступны в C++?
Как правильно повторно выбрасывать исключения?
Типы исключительных ситуаций и процесс их обработки
Заметки докладчика:
- Лекция посвящена обработке исключительных ситуаций в C++ и Qt с акцентом на сетевое программирование
- Связь с лабораторной работой: при парсинге сетевых данных (HTTP-ответы, JSON, бинарные протоколы) ошибки неизбежны — нужно уметь их обрабатывать корректно
- Основной фокус: не только синтаксис try/catch, а проектирование устойчивых сетевых приложений
- В конце лекции студенты должны уметь создавать собственные иерархии исключений для сетевого приложения
Заметки докладчика:
- В сетевом программировании ошибки — не исключение, а норма: обрывы соединения, таймауты, неверные данные от сервера, DNS-ошибки
- Без исключений каждый вызов send()/recv()/connect() нужно проверять вручную — код превращается в «лесенку» из if-else
- Коды возврата (errno, HRESULT) легко проигнорировать — компилятор не заставит проверить; исключения невозможно проигнорировать
- Привести пример: функция connect() возвращает -1 при ошибке, но если программист забыл проверить — неопределённое поведение при следующем send()
Заметки докладчика:
- Ключевое преимущество исключений в C++ — RAII (Resource Acquisition Is Initialization)
- При выбросе исключения происходит раскрутка стека (stack unwinding): для каждого локального объекта вызывается деструктор
- Это значит: открытый сокет, файл, блокировка мьютекса — всё закроется/освободится автоматически
- Без RAII программисту пришлось бы вручную закрывать ресурсы в каждом catch-блоке — легко забыть, что ведёт к утечкам
- Пример: если в функции openConnection() выброшено исключение после socket(), деструктор обёртки вызовет close() автоматически
Заметки докладчика:
- RAII — самый важный паттерн C++, студенты должны его понимать на уровне рефлексии
- Каждый сетевой ресурс (сокет, TCP-соединение, SSL-контекст, файловый дескриптор) должен быть обёрнут в RAII-класс
- std::unique_ptr с кастомным deleter — простой способ обернуть C-ресурс (например, socket → close, FILE* → fclose)
- Qt-классы (QTcpSocket, QFile) сами по себе RAII-обёртки — при разрушении объекта ресурс освобождается
- На практике: если в функции 5 точек, где может произойти ошибка — без RAII нужно 5 раз писать очистку, с RAII — ноль раз
Заметки докладчика:
- std::exception — базовый класс для всех стандартных исключений, всегда ловите через const std::exception&
- Для сетевых ошибок чаще всего подходит std::runtime_error — ошибки, которые нельзя предвидеть при компиляции (обрыв связи, таймаут)
- std::logic_error — ошибки программиста (неверный аргумент, выход за границы), можно отловить при тестировании
- ВАЖНО: ловить исключения только по константной ссылке (const std::exception& e) — иначе будет срезка объекта и потенциальное двойное удаление
- catch (...) — использовать только как последний рубеж, для логирования и корректного завершения
Заметки докладчика:
- В реальных приложениях создавайте иерархию: NetworkException → ConnectionException, TimeoutException, ProtocolException, DataTransmissionException
- Каждое исключение должно хранить контекст: host, port, timestamp, код ошибки ОС (errno/WSAGetLastError()) — это критично для отладки
- Переопределяйте what() и добавляйте getDetails() для структурированного вывода в лог
- Рассмотреть пример: при парсинге HTTP-ответа — ProtocolException с указанием ожидаемого и полученного HTTP-статуса
- Не перегружайте исключения лишними данными — объект копируется при throw, большие строки могут замедлить раскрутку стека
Заметки докладчика:
- У Qt ограниченная поддержка исключений: исключения НЕ пересекают границы сигналов/слотов при использовании queued connections (между потоками)
- QException требует реализации raise() и clone() — это необходимо для корректного перехвата через QFuture/QPromise
- На практике многие Qt-разработчики предпочитают сигналы об ошибках (errorOccurred) и коды возврата вместо исключений
- В асинхронном Qt-коде (QNetworkAccessManager) исключения можно использовать только внутри синхронных обработчиков ответа
- Рекомендация для лабораторных: в синхронных операциях (парсинг, валидация) — исключения, в асинхронных (сетевые запросы) — сигналы об ошибках
Заметки докладчика — ожидаемые ответы:
1. Базовая гарантия (без утечек), сильная гарантия (commit-or-rollback), гарантия отсутствия исключений (noexcept)
2. logic_error — ошибки программиста (предсказуемы), runtime_error — ошибки окружения (непредсказуемы при компиляции)
3. Наследовать от std::exception или std::runtime_error, переопределить what(), обеспечить виртуальный деструктор
4. Деструкторы вызываются автоматически при раскрутке стека — ресурсы (сокеты, файлы, мьютексы) освобождаются без ручного кода
5. Оборачивать тело слота в try/catch, при ошибке генерировать сигнал об ошибке; не пробрасывать через queued connections
6. noexcept указывает, что функция не выбрасывает исключений; использовать для деструкторов, move-операций, критичных к производительности функций
7. std::optional (нет значения), std::variant (типобезопасный union), std::expected (C++23 — значение или ошибка)
8. Использовать throw; (без аргумента) для сохранения оригинального типа исключения и стек-трейса; throw e создаст копию и может потерять тип